// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/update_packages.dart';
import 'package:flutter_tools/src/dart/pub.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:pubspec_parse/pubspec_parse.dart';
import 'package:test/fake.dart';

import '../../src/context.dart';
import '../../src/test_flutter_command_runner.dart';

// An example pubspec.yaml from flutter, not necessary for it to be up to date.
const kFlutterWorkspacePubspecYaml = r'''
name: flutter
description: A framework for writing Flutter applications
homepage: http://flutter.dev

environment:
  sdk: ^3.7.0-0

workspace:
  - packages/flutter
  - examples

dependencies:
  # To update these, use "flutter update-packages --force-upgrade".
  collection: 1.14.11
  meta: 1.1.8
  typed_data: ^1.1.6
  vector_math: 2.0.8
  test_api: 0.7.4

  sky_engine:
    sdk: flutter

  gallery:
    git:
      url: https://github.com/flutter/gallery.git
      ref: d00362e6bdd0f9b30bba337c358b9e4a6e4ca950

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_goldens:
    sdk: flutter

  archive: 3.6.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"

# PUBSPEC CHECKSUM: u6pfsu
''';

const kWidgetTestPubspecYaml = r'''
name: widget_preview_scaffold
description: Scaffolding for Flutter Widget Previews
publish_to: "none"
version: 0.0.1

environment:
  sdk: ^3.8.0-265.0.dev

dependencies:
  flutter:
    sdk: flutter
  flutter_test:
    sdk: flutter
  # These will be replaced with proper constraints after the template is hydrated.
  dtd: 2.5.1
  flutter_lints: 5.0.0
  stack_trace: 1.12.1
  url_launcher: 6.3.1

# PUBSPEC CHECKSUM: giib17
''';

// An example pubspec.yaml from flutter, not necessary for it to be up to date.
const kFlutterPubspecYaml = r'''
name: flutter
description: A framework for writing Flutter applications
homepage: http://flutter.dev

environment:
  sdk: ^3.7.0-0

resolution: workspace

dependencies:
  # To update these, use "flutter update-packages --force-upgrade".
  collection: 1.14.11
  meta: 1.1.8
  typed_data: ^1.1.6
  vector_math: 2.0.8

  sky_engine:
    sdk: flutter

  gallery:
    git:
      url: https://github.com/flutter/gallery.git
      ref: d00362e6bdd0f9b30bba337c358b9e4a6e4ca950

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_goldens:
    sdk: flutter

  archive: 3.6.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"

# PUBSPEC CHECKSUM: dn2ahm
''';

// An example pubspec.yaml, not necessary for it to be up to date.
const kFlutterToolsPubspecYaml = r'''
name: flutter_tools
description: Examples for flutter
homepage: http://flutter.dev

version: 1.0.0

resolution: workspace

environment:
  sdk: '>=3.2.0-0 <4.0.0'
  flutter: ">=2.5.0-6.0.pre.30 <3.0.0"

dependencies:
  test_api: 0.7.4
  flutter:
    sdk: flutter

  archive: 3.6.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"

# PUBSPEC CHECKSUM: 2ut14g
''';

// An example pubspec.yaml, not necessary for it to be up to date.
const kExamplesPubspecYaml = r'''
name: examples
description: Examples for flutter
homepage: http://flutter.dev

version: 1.0.0

resolution: workspace

environment:
  sdk: '>=3.2.0-0 <4.0.0'
  flutter: ">=2.5.0-6.0.pre.30 <3.0.0"

dependencies:
  cupertino_icons: 1.0.4
  flutter:
    sdk: flutter

  archive: 3.6.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"

# PUBSPEC CHECKSUM: ivm9uf
''';

const kVersionJson = '''
{
  "frameworkVersion": "1.2.3",
  "channel": "[user-branch]",
  "repositoryUrl": "git@github.com:flutter/flutter.git",
  "frameworkRevision": "1234567812345678123456781234567812345678",
  "frameworkCommitDate": "2024-02-06 22:26:52 +0100",
  "engineRevision": "abcdef01abcdef01abcdef01abcdef01abcdef01",
  "dartSdkVersion": "1.2.3",
  "devToolsVersion": "1.2.3",
  "flutterVersion": "1.2.3"
}
''';

void main() {
  group('update-packages', () {
    late FileSystem fileSystem;
    late Directory flutterSdk;
    late _FakePub pub;
    late FakeProcessManager processManager;
    late BufferLogger logger;

    setUpAll(() {
      Cache.disableLocking();
      logger = BufferLogger.test();
    });

    setUp(() {
      fileSystem = MemoryFileSystem.test();
      flutterSdk = fileSystem.directory('flutter')..createSync();
      flutterSdk.childFile('version').writeAsStringSync('1.2.3');
      flutterSdk.childDirectory('bin').childDirectory('cache').childFile('flutter.version.json')
        ..createSync(recursive: true)
        ..writeAsStringSync(kVersionJson);
      flutterSdk.childDirectory('dev').createSync(recursive: true);
      flutterSdk.childDirectory('examples').childFile('pubspec.yaml')
        ..createSync(recursive: true)
        ..writeAsStringSync(kExamplesPubspecYaml);
      flutterSdk.childDirectory('packages').childDirectory('flutter_test').childFile('pubspec.yaml')
        ..createSync(recursive: true)
        ..writeAsStringSync(kFlutterToolsPubspecYaml);
      flutterSdk
          .childDirectory('packages')
          .childDirectory('flutter_localizations')
          .childFile('pubspec.yaml')
        ..createSync(recursive: true)
        ..writeAsStringSync(kFlutterToolsPubspecYaml);
      flutterSdk
          .childDirectory('packages')
          .childDirectory('flutter_tools')
          .childFile('pubspec.yaml')
        ..createSync(recursive: true)
        ..writeAsStringSync(kFlutterToolsPubspecYaml);
      flutterSdk.childDirectory('packages').childDirectory('flutter').childFile('pubspec.yaml')
        ..createSync(recursive: true)
        ..writeAsStringSync(kFlutterPubspecYaml);
      flutterSdk.childFile('pubspec.yaml')
        ..createSync()
        ..writeAsStringSync(kFlutterWorkspacePubspecYaml);
      flutterSdk
          .childDirectory('packages')
          .childDirectory('flutter_tools')
          .childDirectory('test')
          .childDirectory('widget_preview_scaffold.shard')
          .childDirectory('widget_preview_scaffold')
          .childFile('pubspec.yaml')
        ..createSync(recursive: true)
        ..writeAsStringSync(kWidgetTestPubspecYaml);
      Cache.flutterRoot = flutterSdk.absolute.path;
      pub = _FakePub();
      processManager = FakeProcessManager.empty();
    });

    testUsingContext(
      'updates packages - only runs pub get',
      () async {
        final command = UpdatePackagesCommand(verboseHelp: false);
        await createTestCommandRunner(command).run(<String>['update-packages']);
        expect(
          pub.pubspecs[flutterSdk.absolute.path]!.first.dependencies,
          Pubspec.parse(kFlutterWorkspacePubspecYaml).dependencies,
        );
      },
      overrides: <Type, Generator>{
        Pub: () => pub,
        FileSystem: () => fileSystem,
        ProcessManager: () => processManager,
        Cache: () => Cache.test(processManager: processManager),
      },
    );

    testUsingContext(
      '--force-upgrade updates packages',
      () async {
        final command = UpdatePackagesCommand(verboseHelp: false);
        await createTestCommandRunner(command).run(<String>['update-packages', '--force-upgrade']);
        expect(
          pub.pubspecs[flutterSdk.absolute.path]!.first.dependencies,
          (Pubspec.parse(kFlutterWorkspacePubspecYaml)
                ..dependencies['typed_data'] = HostedDependency(
                  version: VersionConstraint.parse('^1.1.1'),
                ))
              .dependencies,
        );
      },
      overrides: <Type, Generator>{
        Pub: () => pub,
        FileSystem: () => fileSystem,
        ProcessManager: () => processManager,
        Cache: () => Cache.test(processManager: processManager),
      },
    );

    testUsingContext(
      '--cherry-pick-package',
      () async {
        final command = UpdatePackagesCommand(verboseHelp: false);
        await createTestCommandRunner(
          command,
        ).run(<String>['update-packages', '--cherry-pick=vector_math:2.0.9']);
        expect(
          pub.pubspecs[flutterSdk.absolute.path]!.first.dependencies,
          (Pubspec.parse(kFlutterWorkspacePubspecYaml)
                ..dependencies['vector_math'] = HostedDependency(
                  version: VersionConstraint.parse('2.0.9'),
                ))
              .dependencies,
        );
      },
      overrides: <Type, Generator>{
        Pub: () => pub,
        FileSystem: () => fileSystem,
        ProcessManager: () => processManager,
        Cache: () => Cache.test(processManager: processManager),
        Logger: () => logger,
      },
    );

    testUsingContext(
      '--cherry-pick-package with caret',
      () async {
        final command = UpdatePackagesCommand(verboseHelp: false);
        await createTestCommandRunner(
          command,
        ).run(<String>['update-packages', '--cherry-pick=vector_math:^2.0.9']);
        expect(
          pub.pubspecs[flutterSdk.absolute.path]!.first.dependencies,
          (Pubspec.parse(kFlutterWorkspacePubspecYaml)
                ..dependencies['vector_math'] = HostedDependency(
                  version: VersionConstraint.parse('^2.0.9'),
                ))
              .dependencies,
        );
      },
      overrides: <Type, Generator>{
        Pub: () => pub,
        FileSystem: () => fileSystem,
        ProcessManager: () => processManager,
        Cache: () => Cache.test(processManager: processManager),
        Logger: () => logger,
      },
    );

    testUsingContext(
      '--cherry-pick-package muliple',
      () async {
        final command = UpdatePackagesCommand(verboseHelp: false);
        await createTestCommandRunner(
          command,
        ).run(<String>['update-packages', '--cherry-pick=vector_math:^2.0.9,meta:1.0.5']);
        expect(
          pub.pubspecs[flutterSdk.absolute.path]!.first.dependencies,
          (Pubspec.parse(kFlutterWorkspacePubspecYaml)
                ..dependencies['vector_math'] = HostedDependency(
                  version: VersionConstraint.parse('^2.0.9'),
                )
                ..dependencies['meta'] = HostedDependency(
                  version: VersionConstraint.parse('1.0.5'),
                ))
              .dependencies,
        );
      },
      overrides: <Type, Generator>{
        Pub: () => pub,
        FileSystem: () => fileSystem,
        ProcessManager: () => processManager,
        Cache: () => Cache.test(processManager: processManager),
        Logger: () => logger,
      },
    );

    testUsingContext(
      '--force-upgrade',
      () async {
        final command = UpdatePackagesCommand(verboseHelp: false);
        await createTestCommandRunner(command).run(<String>['update-packages', '--force-upgrade']);
      },
      overrides: <Type, Generator>{
        Pub: () => pub,
        FileSystem: () => fileSystem,
        ProcessManager: () => processManager,
        Cache: () => Cache.test(processManager: processManager),
        Logger: () => logger,
      },
    );
  });
}

class _FakePub extends Fake implements Pub {
  _FakePub();

  var pubspecs = <String, List<Pubspec>>{};

  @override
  Future<void> interactively(
    List<String> arguments, {
    FlutterProject? project,
    required PubContext context,
    required String command,
    bool touchesPackageConfig = false,
    PubOutputMode outputMode = PubOutputMode.all,
  }) async {
    if (project == null) {
      throw ArgumentError('project must not be null');
    }

    Pubspec pubspec;
    if (command == 'add') {
      pubspec = _add(arguments, project: project);
    } else if (command == 'update') {
      pubspec = _upgrade(arguments, project: project);
    } else {
      throw ArgumentError('Unknown command');
    }
    (pubspecs[project.directory.path] ??= <Pubspec>[]).add(pubspec);
  }

  @override
  Future<void> get({
    required PubContext context,
    required FlutterProject project,
    bool upgrade = false,
    bool offline = false,
    String? flutterRootOverride,
    bool checkUpToDate = false,
    bool shouldSkipThirdPartyGenerator = true,
    bool enforceLockfile = false,
    PubOutputMode outputMode = PubOutputMode.all,
  }) async {
    (pubspecs[project.directory.path] ??= <Pubspec>[]).add(
      Pubspec.parse(project.pubspecFile.readAsStringSync()),
    );
  }

  Pubspec _add(List<String> arguments, {required FlutterProject project}) {
    final List<String> split = arguments.first.split(':');
    final String packageName = split[0];
    final String packageVersion = split[1];
    final pubspec = Pubspec.parse(project.pubspecFile.readAsStringSync());
    pubspec.dependencies[packageName] = HostedDependency(
      version: VersionConstraint.parse(packageVersion),
    );
    return pubspec;
  }

  Pubspec _upgrade(List<String> arguments, {required FlutterProject project}) {
    final String pubspec = project.pubspecFile.readAsStringSync();
    project.pubspecFile.writeAsStringSync(
      pubspec.replaceFirst('typed_data: ^1.1.6', 'typed_data: ^1.1.1'),
    );
    return Pubspec.parse(project.pubspecFile.readAsStringSync());
  }
}
